#include "../cse452.h"
#include "../sceneview/MyScene.h"
#include "RenderingInterface.h"
#include <FL/gl.h>
#include <cfloat>

void MyScene::render(int type, int width, int height, unsigned char* pixels) {
    if (!isLoaded) {
        return;
    }
	stopRendering = false;

	renderWidth = width;
	progress = 0.;
    // Add your rendering code here.
    // Keep track of your progress as a value between 0 and 1
    // so the progress bar can update as the rendering progresses
    switch (type) {
        case RenderingUI::RENDER_SCANLINE:  
			renderScanline(width, height, pixels);
			break;
        case RenderingUI::RENDER_RAY_TRACING:  
			renderRayTracing(width, height, pixels);
			break;
        case RenderingUI::RENDER_PATH_TRACING:  
			//Used to test writing to pixels
			for(int j=0; j<height && !stopRendering; ++j){
				for(int i=0; i<width; ++i){
					GLfloat r = static_cast<GLfloat>((float)(i)/(float)(width));
					GLfloat g = static_cast<GLfloat>((float)(j)/(float)(height));
					GLfloat b = static_cast<GLfloat>((float)(i+j)/(float)(width+height));
					putPixel(i, j, Color(r,g,b), pixels);
				}
			}
			break;
        default: break;
    }
}

inline
void MyScene::putPixel(int x, int y, const Color & col, unsigned char* pixels) {
    const int i = (y * renderWidth + x) * 3;
    pixels[i    ] = (unsigned char) (col[0] * 255.0f);
    pixels[i + 1] = (unsigned char) (col[1] * 255.0f);
    pixels[i + 2] = (unsigned char) (col[2] * 255.0f);
}

void MyScene::stopRender()
{
	stopRendering = true;
    // Because this is threaded code, this function
    // can be called in the middle of your rendering code.
    // You should then stop at the next scanline
}

double MyScene::getRenderProgress() {
    // return the current progress as a value between 0 and 1
    return progress;
}

// add extra methods here


Hit MyScene::rayToObject(const Vector3 ray, const Point3 point, MatrixObject*& hitObject){
	double bestT = DBL_MAX, t, u, v;
	Point3 objPoint, hitPoint;
	Vector3 objDir, hitNormal;
	HitRecord objHits;
	Hit bestHit;
	
	for(std::list<MatrixObject>::iterator it = objectList.begin(); it != objectList.end(); ++it){
		objDir = it->im * ray;
		objPoint = it->im * point;
		objHits = it->o->intersect(objPoint, objDir);
		objHits.sortHits();
		if(objHits.getFirstHit(t, u, v, hitPoint, hitNormal)){//found hit
			if(t < bestT && t > 0.){
				bestT = t;
				hitObject = &(*it);
				bestHit = objHits.getFirstHitRecord();
			}
		}
	}
	return bestHit;
}

void MyScene::renderScanline(int width, int height, unsigned char* pixels){
	for(int j=0; j<height && !stopRendering; ++j){
		for(int i=0; i<width; ++i){
			int cur = (i + (height - 1 - j)*width)*3;
			lightAtPoint(i, j, pixels[cur], pixels[cur+1], pixels[cur+2], 0);
		}
		progress = (double)(j+1)/(double)(height);
		Fl::check();
	}
}

void MyScene::renderRayTracing(int width, int height, unsigned char* pixels){
	for(int j=0; j<height && !stopRendering; ++j){
		for(int i=0; i<width; ++i){
			int cur = (i + (height - 1 - j)*width)*3;
			lightAtPoint(i, j, pixels[cur], pixels[cur+1], pixels[cur+2]);
		}
		progress = (double)(j+1)/(double)(height);
		Fl::check();
	}
}

void MyScene::lightAtPoint(const int x, const int y, unsigned char& r, unsigned char& g, unsigned char& b, const int depth){
	Vector3 ray = makeRay(x, y);
	ray.normalize();
	lightAlongRay(ray, camera.getEye(), r, g, b, depth);
}


void MyScene::lightAlongRay(const Vector3& ray, const Point3& point, unsigned char& r, unsigned char& g, unsigned char& b, const int levels){
	MatrixObject* obj = 0;
	Hit hit = rayToObject(ray, point, obj);
	double rc = 0., gc = 0., bc = 0., 
		rdif = 0., gdif = 0., bdif = 0., 
		ramb = 0., gamb = 0., bamb = 0.,
		rref = 0., gref = 0., bref = 0.,
		rtrans = 0., gtrans = 0., btrans = 0.,
		rspec = 0., gspec = 0., bspec = 0.;

	if(obj != 0){
		
		Vector3 toLight, reflect;
		double diff, spec, att;
		Color lightColor;
		Hit obscHit;
		MatrixObject* obscObj;

		hit.p = obj->m * hit.p;
		hit.n = obj->im.transpose() * hit.n;
		hit.n.normalize();

		///////////// REFLECT /////////////////////////
		Color objReflect = obj->o->getReflect();
		if(levels > 0 && objReflect.getMax() > 0.){
			unsigned char crref = 0, cgref = 0, cbref = 0;
			reflect = ray;
			reflect.normalize();
			reflect = (2*(hit.n * -reflect)) * hit.n + reflect;
			reflect.normalize();
			lightAlongRay(reflect, hit.p + reflect * 0.00001, crref, cgref, cbref, (levels - 1));

			rref = ((float)(crref))/255. * objReflect[0];
			gref = ((float)(cgref))/255. * objReflect[1];
			bref = ((float)(cbref))/255. * objReflect[2];
		}

		///////////// TRANSPARENCY ////////////////////
		Color objTrans = obj->o->getTransparent();
		if(levels > 0 && objReflect.getMax() > 0.){
			unsigned char crtrans = 0, cgtrans = 0, cbtrans = 0;
			
			lightAlongRay(ray, hit.p + ray * 0.00001, crtrans, cgtrans, cbtrans, (levels - 1));

			rtrans = ((float)(crtrans))/255. * objTrans[0];
			gtrans = ((float)(cgtrans))/255. * objTrans[1];
			btrans = ((float)(cbtrans))/255. * objTrans[2];
		}




		//////////// AMBIENT //////////////////////////
		ramb = ambientLight[0] * obj->o->getAmbient()[0];
		gamb = ambientLight[1] * obj->o->getAmbient()[1];
		bamb = ambientLight[2] * obj->o->getAmbient()[2];



		/////////// DIFFUSE AND SPECULAR ///////////////////
		for(std::vector<Light>::iterator light = lights.begin(); light != lights.end(); ++light){
			toLight = light->getPos() - hit.p;
			
			obscObj = 0;

			//Need to transform from object space into world space
			obscHit = rayToObject(toLight, hit.p + toLight * 0.00001, obscObj);
			
			
			if(obscObj != 0){
				obscHit.p = obscObj->m * obscHit.p;
				//If uses obscHit.n is used transform it
				/*obscHit.n = obscObj->im.transpose() * obscHit.n;
				obscHit.n.normalize();*/
			}
			//If not in shadow
			if(obscObj == 0 || (hit.p - obscHit.p).lengthSquared() > toLight.lengthSquared()){
			
				att = 1. / (light->getFalloff()[0] + light->getFalloff()[1] * toLight.length() + light->getFalloff()[2] * toLight.lengthSquared());
				toLight.normalize();
				reflect = (2*(hit.n * -toLight)) * hit.n + toLight;
				reflect.normalize();
				
				diff = hit.n * toLight;
				if(diff < 0.)
					diff = 0.;
				spec = (ray * reflect);
				spec = (spec > 0.) ? pow((spec), obj->o->getShine()) : 0.;
				lightColor = light->getColor();
				rdif	+= lightColor[0] * att * obj->o->getDiffuse()[0] * diff;
				rspec	+= lightColor[0] * att * obj->o->getSpecular()[0] * spec;// * (1. - rc);
				gdif	+= lightColor[1] * att * obj->o->getDiffuse()[1] * diff;
				gspec	+= lightColor[1] * att * obj->o->getSpecular()[1] * spec;// * (1. - gc);
				bdif	+= lightColor[2] * att * obj->o->getDiffuse()[2] * diff;
				bspec	+= lightColor[2] * att * obj->o->getSpecular()[2] * spec;// * (1. - bc);
			}
		}
		
		//////////////// PREVENT OVERFLOW ////////////////

		rc = rdif + ramb + rref + rspec + rtrans;
		gc = gdif + gamb + gref + gspec + gtrans;
		bc = bdif + bamb + bref + bspec + btrans;


		if(rc > 1.) rc = 1.;
		else if(rc < 0.) rc = 0.;
		if(gc > 1.) gc = 1.;
		else if(gc < 0.) gc = 0.;
		if(bc > 1.) bc = 1.;
		else if(bc < 0.) bc = 0.;

#ifdef _DEBUG
		if(rc > 1. || rc < 0.)
			std::cout << "Color overflow " << rc << std::endl;
#endif
	}else{//not hit object
		rc = background[0];
		gc = background[1];
		bc = background[2];
	}

	//////////// OUTPUT //////////////
	r = (unsigned char)(rc * 255.0f);
	g = (unsigned char)(gc * 255.0f);
	b = (unsigned char)(bc * 255.0f);
	
}
